package com.lightsaber.trade.cloth.service.planning.impl;

import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springside.modules.orm.Page;

import com.lightsaber.trade.core.dao.BaseDao;
import com.lightsaber.trade.core.entity.AuditedObject;
import com.lightsaber.trade.core.entity.BaseEntity;
import com.lightsaber.trade.core.service.HppBaseServiceImpl;
import com.lightsaber.trade.cloth.dao.archive.ArchivedImageDao;
import com.lightsaber.trade.cloth.dao.category.CategoryDao;
import com.lightsaber.trade.cloth.dao.category.CategoryNodeDao;
import com.lightsaber.trade.cloth.dao.category.CategoryOwnerDao;
import com.lightsaber.trade.cloth.dao.category.CategoryPlannerDao;
import com.lightsaber.trade.cloth.dao.category.CategoryProgrammeAttributeDao;
import com.lightsaber.trade.cloth.dao.planning.CategoryPlanningItemDao;
import com.lightsaber.trade.cloth.dao.planning.DisciplinePlanningItemDao;
import com.lightsaber.trade.cloth.dao.planning.Level1PlanningDao;
import com.lightsaber.trade.cloth.dao.planning.PlanningDao;
import com.lightsaber.trade.cloth.entity.archive.ArchivedImage;
import com.lightsaber.trade.cloth.entity.category.Category;
import com.lightsaber.trade.cloth.entity.category.CategoryBranch;
import com.lightsaber.trade.cloth.entity.category.CategoryLeaf;
import com.lightsaber.trade.cloth.entity.category.CategoryNode;
import com.lightsaber.trade.cloth.entity.category.CategoryOwner;
import com.lightsaber.trade.cloth.entity.category.CategoryPlanner;
import com.lightsaber.trade.cloth.entity.category.CategoryProgrammeAttribute;
import com.lightsaber.trade.cloth.entity.planning.CategoryPlanningItem;
import com.lightsaber.trade.cloth.entity.planning.DisciplinePlanningItem;
import com.lightsaber.trade.cloth.entity.planning.Level1Planning;
import com.lightsaber.trade.cloth.entity.planning.Planning;
import com.lightsaber.trade.cloth.entity.planning.PlanningLevel;
import com.lightsaber.trade.cloth.entity.planning.PlanningStatus;
import com.lightsaber.trade.cloth.service.planning.PlanningService;

@Service
@Component
@Transactional
public class PlanningServiceImpl extends HppBaseServiceImpl<Planning, String> implements
        PlanningService {

    private final static Logger log = LoggerFactory.getLogger(PlanningServiceImpl.class);

    @Autowired
    private CategoryDao categoryDao;

    @Autowired
    private CategoryNodeDao categoryNodeDao;

    @Autowired
    private PlanningDao planningDao;

    @Autowired
    private CategoryProgrammeAttributeDao categoryProgrammeAttributeDao;

    @Autowired
    private Level1PlanningDao level1PlanningDao;

    @Autowired
    private CategoryPlanningItemDao categoryPlanningItemDao;

    @Autowired
    private DisciplinePlanningItemDao disciplinePlanningItemDao;

    @Autowired
    private CategoryOwnerDao categoryOwnerDao;

    @Autowired
    private CategoryPlannerDao categoryPlannerDao;

    @Autowired
    private ArchivedImageDao archivedImageDao;

    @Override
    protected BaseDao<Planning, String> getDao() {
        return planningDao;
    }

    @Override
    public void save(final Planning planning) {
        if (StringUtils.isBlank(planning.getId())) {
            planningDao.save(planning);
        } else {
            Planning old = get(planning.getId());
            BeanUtils.copyProperties(planning, old, new String[] { BaseEntity.ID, BaseEntity.VERSION,
                    "category", AuditedObject.CREATED_BY, AuditedObject.CREATED_DATE,
                    "planningLevel", "level1Planning", "level2Planning", "level3Planning" });
            planningDao.update(old);
        }

    }

    @Override
    public Planning findByCategoryIdAndPlanningLevel(final String categoryId,
            final PlanningLevel planningLevel) {
        List<Planning> planningList = planningDao.findByCategoryIdAndPlanningLevel(categoryId,
                planningLevel);
        if (planningList.isEmpty()) {
            return null;
        } else {
            return planningList.get(0);
        }

    }

    @Override
    public List<Planning> findByCategoryId(final String categoryId) {
        List<Planning> planningList = planningDao.findBy("category.id", categoryId);
        return planningList;

    }

    @Override
    public Page<Planning> findPage(final Planning planning, final Page<Planning> page) {
        return planningDao.findPage(planning, page);
    }

    @Override
    public Page<Planning> search(final Integer planningExercise, final PlanningLevel planningLevel,
            final Page<Planning> page) {
        return planningDao.findPage(planningExercise, planningLevel, false, page);
    }

    @Override
    public void endorseFirstLevelPlanning(final Category masterTree,
            final Map<CategoryNode, Level1Planning> selectedPlannings, final String name,
            final String remarks) {

        log.info("endorsing First Level Planning, masterTree id: {}, name: {}", masterTree.getId(),
                masterTree.getPlanningExerciseName());

        // update first level planning status
        Level1Planning level1Planning = updatePlanningStatus(masterTree, name, remarks);

        Set<Map.Entry<CategoryNode, Level1Planning>> entrySet = selectedPlannings.entrySet();
        for (Map.Entry<CategoryNode, Level1Planning> entry : entrySet) {
            CategoryNode masterNode = entry.getKey();
            Level1Planning selectedPlanning = entry.getValue();

            Category branchTree = selectedPlanning.getCategory();
            CategoryNode ownerNode = branchTree.getOwnerNode();
            assert masterNode.getId().equals(ownerNode.getId());

            // ------------ Begin splitting joint Branch ------------

            for (Category otherBranchTree : ownerNode.getOwnedBranchTrees()) {
                Level1Planning otherPlanning = level1PlanningDao.findByBranchTree(otherBranchTree);

                if (otherPlanning.getStatus() == PlanningStatus.Endorsed
                        || otherPlanning.getStatus() == PlanningStatus.NotEndorsed
                        || otherPlanning.getStatus() == PlanningStatus.WaitingForEndorsement) {

                    if (otherPlanning.getId().equals(selectedPlanning.getId())) {
                        otherPlanning.setStatus(PlanningStatus.Endorsed);
                    } else {
                        otherPlanning.setStatus(PlanningStatus.NotEndorsed);
                    }

                    level1PlanningDao.update(otherPlanning);
                }
            }

            Map<String, String> idMappings = new HashMap<String, String>();

            // copy branch tree to master tree
            copyBranchTreeToMasterTree(branchTree, ownerNode, idMappings);

            // copy selected planning to master tree
            copyCategoryPlanningItem(level1Planning, selectedPlanning, idMappings);
            copyDisciplinePlanningItem(level1Planning, selectedPlanning, idMappings);
        }

    }

    private Level1Planning updatePlanningStatus(final Category masterTree, final String name,
            final String remarks) {
        Planning planning = findByCategoryIdAndPlanningLevel(masterTree.getId(),
                PlanningLevel.FirstLevel);
        if (planning == null) {
            log.info("first level planning not found, create a new one");
            planning = new Planning();
            planning.setCategory(masterTree);
            planning.setIsArchivedImage(false);
            planning.setPlanningLevel(PlanningLevel.FirstLevel);
        }
        planning.setStatus(PlanningStatus.Endorsed);
        planning.setEndorsedDate(new Date());
        planningDao.save(planning);

        Level1Planning level1Planning = new Level1Planning();
        level1Planning.setCategory(masterTree);
        level1Planning.setName(name);
        level1Planning.setPlanningExercise(masterTree.getPlanYear());
        level1Planning.setRemarks(remarks);
        level1Planning.setStatus(PlanningStatus.Endorsed);
        level1Planning.setPlanning(planning);
        level1PlanningDao.save(level1Planning);

        return level1Planning;
    }

    private void copyBranchTreeToMasterTree(final Category branchTree,
            final CategoryNode ownerNode, final Map<String, String> idMappings) {
        CategoryNode branchRoot = branchTree.getRoot();
        saveTo(branchRoot, ownerNode, idMappings, null);
    }

    private void copyCategoryPlanningItem(final Level1Planning level1Planning,
            final Level1Planning selectedPlanning, final Map<String, String> idMappings) {
        List<CategoryPlanningItem> categoryPlanningItems = selectedPlanning
                .getCategoryPlanningItems();
        for (CategoryPlanningItem fromItem : categoryPlanningItems) {
            CategoryPlanningItem toItem = new CategoryPlanningItem();

            toItem.setActualEnrolment(fromItem.getActualEnrolment());
            CategoryLeaf categoryLeaf = (CategoryLeaf) categoryNodeDao.load(idMappings.get(fromItem
                    .getCategoryLeaf().getId()));
            toItem.setCategoryLeaf(categoryLeaf);
            toItem.setLevel1Planning(level1Planning);
            toItem.setConvertRate(fromItem.getConvertRate());
            toItem.setPlanedTrainingHours(fromItem.getPlanedTrainingHours());
            toItem.setPlannedPlaces(fromItem.getPlannedPlaces());
            toItem.setPlanYear(fromItem.getPlanYear());

            categoryPlanningItemDao.save(toItem);
        }
    }

    private void copyDisciplinePlanningItem(final Level1Planning level1Planning,
            final Level1Planning selectedPlanning, final Map<String, String> idMappings) {
        List<DisciplinePlanningItem> disciplinePlanningItems = selectedPlanning
                .getDisciplinePlanningItems();
        for (DisciplinePlanningItem fromItem : disciplinePlanningItems) {
            DisciplinePlanningItem toItem = new DisciplinePlanningItem();
            toItem.setActualEnrolment(fromItem.getActualEnrolment());
            toItem.setCategoryNode(categoryNodeDao.load(idMappings.get(fromItem.getCategoryNode()
                    .getId())));
            toItem.setDiscipline(fromItem.getDiscipline());
            toItem.setFundingMode(fromItem.getFundingMode());
            toItem.setLevel1Planning(level1Planning);
            toItem.setPlannedNew(fromItem.getPlannedNew());
            toItem.setPlannedPlaces(fromItem.getPlannedPlaces());
            toItem.setPlannedProgressing(fromItem.getPlannedProgressing());
            toItem.setPlanYear(fromItem.getPlanYear());

            disciplinePlanningItemDao.save(toItem);
        }
    }

    private void copyChildren(final CategoryNode fromNode, final CategoryNode toNode,
            final Map<String, String> idMappings,
            final Map<CategoryNode, Level1Planning> selectedPlannings) {
        List<CategoryNode> children = fromNode.getChildren();
        for (CategoryNode child : children) {

            CategoryNode newNode = null;

            if (child instanceof CategoryLeaf) {
                newNode = new CategoryLeaf();
            } else {
                newNode = new CategoryBranch();
            }
            newNode.setParent(toNode);

            saveTo(child, newNode, idMappings, selectedPlannings);
        }
    }

    private void saveTo(final CategoryNode from, final CategoryNode to,
            final Map<String, String> idMappings,
            final Map<CategoryNode, Level1Planning> selectedPlannings) {
        to.setInputType(from.getInputType());
        to.setIsDisciplineView(from.getIsDisciplineView());
        to.setIsInheritParentNode(from.getIsInheritParentNode());
        to.setName(from.getName());
        to.setRemarks(from.getRemarks());
        to.setSourceType(from.getSourceType());

        to.setLevel2status(PlanningStatus.WaitingForSubmission);

        if (to instanceof CategoryLeaf) {
            CategoryLeaf toLeaf = (CategoryLeaf) to;
            CategoryLeaf fromLeaf = (CategoryLeaf) from;
            toLeaf.setFundingMode(fromLeaf.getFundingMode());
        }

        categoryNodeDao.save(to);
        idMappings.put(from.getId(), to.getId());

        if (to instanceof CategoryLeaf) {
            copyCategoryProgrammeAttribute((CategoryLeaf) from, (CategoryLeaf) to);
        }

        copyCategoryOwner(from, to);
        copyCategoryPlanner(from, to);

        if (selectedPlannings != null) {// for archive only
            Level1Planning selectedPlanning = selectedPlannings.get(from);
            if (selectedPlanning != null) {
                Category selectedBranchTree = selectedPlanning.getCategory();
                copySelectedBranch(selectedBranchTree, to, idMappings, selectedPlannings);
            }
        }

        copyChildren(from, to, idMappings, selectedPlannings);
    }

    private void copyCategoryProgrammeAttribute(final CategoryLeaf from, final CategoryLeaf to) {
        List<CategoryProgrammeAttribute> attributs = from.getCategoryProgrammeAttributes();
        for (CategoryProgrammeAttribute fromAttr : attributs) {
            CategoryProgrammeAttribute toAttr = new CategoryProgrammeAttribute();
            toAttr.setCategoryLeaf(to);
            toAttr.setProgrammeAttribute(fromAttr.getProgrammeAttribute());
            toAttr.setValue(fromAttr.getValue());
            categoryProgrammeAttributeDao.save(toAttr);
        }
    }

    private void copyCategoryOwner(final CategoryNode from, final CategoryNode to) {
        List<CategoryOwner> owners = from.getCategoryOwners();
        for (CategoryOwner fromOwner : owners) {
            CategoryOwner toOwner = new CategoryOwner();
            toOwner.setCategoryNode(to);
            toOwner.setRoleCode(fromOwner.getRoleCode());

            categoryOwnerDao.save(toOwner);
        }
    }

    private void copyCategoryPlanner(final CategoryNode from, final CategoryNode to) {
        List<CategoryPlanner> planners = from.getCategoryPlanners();
        for (CategoryPlanner fromPlanner : planners) {
            CategoryPlanner toPlanner = new CategoryPlanner();
            toPlanner.setCategoryNode(to);
            toPlanner.setRoleCode(fromPlanner.getRoleCode());

            categoryPlannerDao.save(toPlanner);
        }
    }

    private void copySelectedBranch(final Category selectedBranchTree, final CategoryNode to,
            final Map<String, String> idMappings,
            final Map<CategoryNode, Level1Planning> selectedPlannings) {
        Category toBranch = new Category();
        toBranch.setIsArchivedImage(true);
        toBranch.setOwnerNode(to);

        copyCategory(selectedBranchTree, toBranch, idMappings, selectedPlannings);
    }

    @Override
    public void archiveFirstLevelPlanning(final Category masterCategory,
            final Map<CategoryNode, Level1Planning> selectedPlannings, final String name,
            final String remarks) {

        // 1. archive master tree and its branch tree
        Category imageCategory = new Category();
        imageCategory.setIsArchivedImage(true);

        Map<String, String> idMappings = new HashMap<String, String>();

        copyCategory(masterCategory, imageCategory, idMappings, selectedPlannings);

        // 2. archive planning item
        Planning imagePlanning = saveImagePlanning(imageCategory);

        saveArchivedImage(masterCategory, name, remarks, imageCategory, imagePlanning);

        Set<Map.Entry<CategoryNode, Level1Planning>> entrySet = selectedPlannings.entrySet();
        for (Map.Entry<CategoryNode, Level1Planning> entry : entrySet) {
            CategoryNode masterNode = entry.getKey();
            Level1Planning selectedPlanning = entry.getValue();

            Category branchTree = selectedPlanning.getCategory();
            CategoryNode ownerNode = branchTree.getOwnerNode();
            assert masterNode.getId().equals(ownerNode.getId());

            String createdId = idMappings.get(branchTree.getId());
            Category createdBranchTree = categoryDao.get(createdId);

            Level1Planning level1Planning = saveLevel1Planning(selectedPlanning, createdBranchTree);

            // copy selected planning to image
            copyCategoryPlanningItem(level1Planning, selectedPlanning, idMappings);
            copyDisciplinePlanningItem(level1Planning, selectedPlanning, idMappings);
        }

    }

    private Level1Planning saveLevel1Planning(final Level1Planning selectedPlanning,
            final Category createdBranchTree) {
        Level1Planning level1Planning = new Level1Planning();
        level1Planning.setCategory(createdBranchTree);
        level1Planning.setName(selectedPlanning.getName());
        level1Planning.setPlanning(null);
        level1Planning.setPlanningExercise(selectedPlanning.getPlanningExercise());
        level1Planning.setRemarks(selectedPlanning.getRemarks());
        level1Planning.setStatus(selectedPlanning.getStatus());
        level1PlanningDao.save(level1Planning);
        return level1Planning;
    }

    private void copyCategory(final Category fromCategory, final Category toCategory,
            final Map<String, String> idMappings,
            final Map<CategoryNode, Level1Planning> selectedPlannings) {
        saveToImage(fromCategory, toCategory);
        idMappings.put(fromCategory.getId(), toCategory.getId());

        CategoryNode masterRoot = fromCategory.getRoot();
        CategoryNode imageRoot = new CategoryBranch();
        imageRoot.setCategory(toCategory);
        saveTo(masterRoot, imageRoot, idMappings, selectedPlannings);
    }

    private void saveToImage(final Category from, final Category to) {

        to.setBranchName(from.getBranchName());
        to.setEndorsementStatus(from.getEndorsementStatus());
        to.setEndorsementTime(from.getEndorsementTime());
        to.setLevel1End(from.getLevel1End());
        to.setLevel1Start(from.getLevel1Start());
        to.setLevel2End(from.getLevel2End());
        to.setLevel2Start(from.getLevel2Start());
        to.setLevel3End(from.getLevel3End());
        to.setLevel3Start(from.getLevel3Start());
        to.setPlanningExerciseName(from.getPlanningExerciseName());
        to.setPlanYear(from.getPlanYear());
        to.setRemarks(from.getRemarks());

        categoryDao.save(to);
    }

    private Planning saveImagePlanning(final Category imageCategory) {
        Planning imagePlanning = new Planning();
        imagePlanning.setCategory(imageCategory);
        imagePlanning.setEndorsedDate(null);
        imagePlanning.setIsArchivedImage(true);
        imagePlanning.setPlanningLevel(PlanningLevel.FirstLevel);
        imagePlanning.setStatus(PlanningStatus.WaitingForSubmission);
        planningDao.save(imagePlanning);
        return imagePlanning;
    }

    private void saveArchivedImage(final Category masterCategory, final String name,
            final String remarks, final Category imageCategory, final Planning imagePlanning) {
        ArchivedImage archivedImage = new ArchivedImage();
        archivedImage.setCategory(imageCategory);
        archivedImage.setName(name);
        archivedImage.setPlanning(imagePlanning);
        archivedImage.setPlanningExercise(masterCategory.getPlanYear());
        archivedImage.setRemarks(remarks);
        archivedImageDao.save(archivedImage);
    }

    @Override
    public Planning findById(final String id) {
        return planningDao.get(id);
    }

}
